<!--
@license
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../paper-dropdown-menu/paper-dropdown-menu.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-menu/paper-menu.html">
<link rel="import" href="../paper-radio-group/paper-radio-group.html">
<link rel="import" href="../paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../paper-tooltip/paper-tooltip.html">
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
<link rel="import" href="../tf-graph-common/tf-graph-common.html">

<dom-module id="tf-graph-controls">
<template>
<style>
:host {
  font-size: 12px;
  color: gray;
  --paper-font-subhead: {
    font-size: 14px;
    color: gray;
  };
  --paper-dropdown-menu-icon: {
    width: 15px;
    height: 15px;
  };
  --paper-dropdown-menu-button: {
    padding: 0;
  };
  --paper-dropdown-menu-input: {
    padding: 0;
  };
  --paper-item-min-height: 30px;
}

paper-button[raised].keyboard-focus {
  font-weight: normal;
}

.run-dropdown {
  --paper-input-container: {
    padding: 9px 0 0 25px;
  };
}

.color-dropdown {
  --paper-input-container: {
    padding: 9px 0 0 13px;
  };
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

table td {
  padding: 0;
  margin: 0;
}

.allcontrols {
  width: 188px;
  padding: 0 30px;
}

.legend-holder {
  background: #e9e9e9;
  border-top: 1px solid #ccc;
  color: #555;
  position: absolute;
  bottom: 0;
  left: 0;
  padding: 15px 23px;
  width: 100%;
}

.toggle-legend-button {
  max-height: 20px;
  max-width: 20px;
  padding: 0;
}

.toggle-legend-text {
  vertical-align: middle;
}

paper-radio-button {
  display: block;
  padding: 5px;
}
svg.icon {
  width: 60px;
  height: 18px;
}
.icon ellipse {
  rx: 10px;
  ry: 5px;
  stroke: #CCC;
  stroke-width: 1px;
  fill: #FFFFFF;
  cy: 10px;
}
.icon rect {
  height: 14px;
  width: 35px;
  rx: 5px;
  ry: 5px;
  stroke: #CCC;
  stroke-width: 2px;
  fill: #D9D9D9;
}
.domainValues {
  margin-bottom: 10px;
  width: 165px;
}
.domainStart {
  float: left;
}
.domainEnd {
  float: right;
}
.colorBox {
  width: 20px;
}

.image-icon {
  width: 24px;
  height: 24px;
}

.help-icon {
  height: 15px;
  margin: 0;
  padding: 0;
}

.gray {
  color: #666;
}

.title {
  font-size: 16px;
  margin: 8px 5px 8px 0;
  color: black;
}
.title small {
  font-weight: normal;
}
.deviceList, .xlaClusterList {
  max-height: 200px;
  overflow-y: auto;
}

#file {
  padding: 8px 0;
}

.color-legend-row {
  clear: both;
  height: 20px;
  margin-top: 5px;
  position: relative;
}

.color-legend-row svg {
  position: absolute;
  top: -1px;
  width: 40px;
}

.color-legend-row span.color-legend-value {
  margin-left: 60px;
}

#grey-rect {
  fill: #eee;
  stroke: #a6a6a6;
}

#faded-rect {
  fill: url(#rectHatch);
  stroke: var(--tb-graph-faded);
}

#unfilled-rect {
  stroke: #a6a6a6;
}

.devices-checkbox input {
  text-align: left;
  vertical-align: middle
}

.button-text {
  text-transform: none;
  padding: 8px 18px 0 18px;
  font-size: 14px
}

.upload-button {
  width: 165px;
  height: 25px;
  text-transform: none;
  margin-top: 4px;
}

.iconbutton {
  padding: 2px;
  width: 30px;
  height: 30px;
  color: var(--paper-orange-500);
}

.hidden-input {
  height: 0px;
  width: 0px;
  overflow:hidden;
}

.allcontrols .control-holder {
  display: flex;
  clear: both;
}

.allcontrols .control-holder paper-radio-group {
  margin-top: 5px;
}

span.counter {
  font-size: 13px;
  color: gray;
}

.runs paper-item {
  --paper-item: {
    white-space: nowrap;
  }
}

table.control-holder {
  border: 0;
  border-collapse: collapse;
}

table.tf-graph-controls td.input-element-table-data {
  padding: 0 0 0 20px;
}

/** Override inline styles that suppress pointer events for disabled buttons. Otherwise, the */
/*  tooltips do not appear. */
#color-by-radio-group paper-radio-button {
  pointer-events: auto !important;
}

.legend-clarifier {
  color: #266236;
  cursor: help;
  display: inline-block;
  text-decoration: underline;
}

.legend-clarifier paper-tooltip {
  width: 150px;
}
</style>
<svg width="0" height="0">
  <defs>
    <g id="legend-rect">
      <rect x="1" y="1" stroke-width="2px" height="14" width="35" rx="5" ry="5"></rect>
    </g>
    <g id="grey-rect">
       <use xmlns:xlink="http://www.w3.org/1999/xlink"
            xlink:href="#legend-rect"/>
     </g>
     <g id="faded-rect">
       <use xmlns:xlink="http://www.w3.org/1999/xlink"
            xlink:href="#legend-rect"/>
     </g>
     <g id="unfilled-rect">
       <use xmlns:xlink="http://www.w3.org/1999/xlink"
            xlink:href="#legend-rect"/>
     </g>
  </defs>
</svg>
<div class="allcontrols">
  <div class="control-holder">
    <paper-icon-button icon="aspect-ratio" class="iconbutton" on-click="fit" alt="Fit to screen">
    </paper-icon-button>
    <paper-button class="button-text" on-click="fit">Fit to screen
    </paper-button>
  </div>
  <div class="control-holder">
    <paper-icon-button icon="file-download" class="iconbutton" on-click="download" alt="Download PNG">
    </paper-icon-button>
    <paper-button class="button-text" on-click="download">Download PNG
    </paper-button>
    <a href="#" id="graphdownload" class="title" download="graph.png">
    </a>
  </div>
  <div class="control-holder runs">
    <div class="title">Run <span class="counter">([[datasets.length]])</span></div>
    <paper-dropdown-menu no-label-float no-animations noink class="run-dropdown">
      <paper-menu id="select" class="dropdown-content" selected="{{selectedDataset}}" slot="dropdown-content">
        <template is="dom-repeat" items="[[datasets]]">
          <paper-item>[[item.name]]</paper-item>
        </template>
      </paper-menu>
    </paper-dropdown-menu>
  </div>
  <template is="dom-if" if="[[showSessionRunsDropdown]]">
    <div class="control-holder">
      <div class="title">Session runs <span class="counter">([[_numSessionRuns(metadataTags)]])</span></div>
      <paper-dropdown-menu no-label-float no-animations noink class="run-dropdown">
        <paper-menu id="select" class="dropdown-content" selected="{{selectedMetadataTag}}" slot="dropdown-content">
          <template is="dom-repeat" items="[[metadataTags]]">
            <paper-item>[[item.tag]]</paper-item>
          </template>
          <paper-item>None</paper-item>
        </paper-menu>
      </paper-dropdown-menu>
    </div>
  </template>
  <template is="dom-if" if="[[showUploadButton]]">
    <div class="control-holder">
      <div class="title">Upload</div>
      <paper-button raised class="text-button upload-button"
          on-click="_getFile">Choose File</paper-button>
      <div class="hidden-input">
        <input type="file" id="file" name="file" on-change="_updateFileInput" />
      </div>
    </div>
  </template>
  <div class="control-holder">
    <paper-toggle-button id="trace-inputs"></paper-toggle-button>
    <div class="title">Trace inputs</div>
  </div>
  <template is="dom-if" if="[[healthPillsFeatureEnabled]]">
    <div class="control-holder">
      <paper-toggle-button checked="{{healthPillsToggledOn}}"></paper-toggle-button>
      <div class="title">Show health pills</div>
    </div>
  </template>
  <div class="control-holder">
    <div class="title">Color</div>
    <paper-radio-group id="color-by-radio-group" selected="{{colorBy}}">
      <paper-radio-button name="structure">Structure</paper-radio-button>

      <paper-radio-button name="device">Device</paper-radio-button>

      <paper-radio-button id="xla-cluster-radio-button"
                          name="xla_cluster"
                          disabled="[[!_xlaClustersProvided(renderHierarchy)]]">
        XLA Cluster
      </paper-radio-button>
      <paper-tooltip animation-delay="0" for="xla-cluster-radio-button" position="right">
        Coloring by XLA cluster is only enabled if at least 1 op specifies an XLA cluster.
      </paper-tooltip>

      <paper-radio-button id="compute-time-radio-button"
                          name="compute_time"
                          disabled="[[!stats]]">
        Compute time
      </paper-radio-button>
      <paper-tooltip animation-delay="0" for="compute-time-radio-button" position="right">
        Coloring by compute time is only enabled if the RunMetadata proto is passed to the
        FileWriter when a specific session is run.
      </paper-tooltip>

      <paper-radio-button id="memory-radio-button"
                          name="memory"
                          disabled="[[!stats]]">
        Memory
      </paper-radio-button>
      <paper-tooltip animation-delay="0" for="memory-radio-button" position="right">
        Coloring by memory is only enabled if the RunMetadata proto is passed to the
        FileWriter when a specific session is run.
      </paper-tooltip>

      <paper-radio-button id="tpu-compatibility-radio-button"
                          name="op_compatibility">
        TPU Compatibility
      </paper-radio-button>
      <paper-tooltip animation-delay="0" for="tpu-compatibility-radio-button" position="right">
        Coloring by whether an operation is compatible for the TPU device.
      </paper-tooltip>

    </paper-radio-group>
  </div>
  <div>
    <template is="dom-if" if="[[_isGradientColoring(stats, colorBy)]]">
      <svg width="140" height="20" style="margin: 0 5px" class="color-text">
        <defs>
          <linearGradient id="linearGradient" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop class="start" offset="0%"
                stop-color$="[[_currentGradientParams.startColor]]"/>
            <stop class="end" offset="100%"
                stop-color$="[[_currentGradientParams.endColor]]"/>
          </linearGradient>
        </defs>
        <rect x="0" y="0" width="135" height="20" fill="url(#linearGradient)"
            stroke="black" />
      </svg>
      <div class="domainValues color-text">
        <div class="domainStart">[[_currentGradientParams.minValue]]</div>
        <div class="domainEnd">[[_currentGradientParams.maxValue]]</div>
      </div>
      <br style="clear: both">
      <div>Devices included in stats:</div>
      <div class="deviceList">
        <template is="dom-repeat" items="[[_currentDevices]]">
          <div class="color-legend-row devices-checkbox">
            <span><input type="checkbox" value$="[[item.device]]" checked$="[[item.used]]" on-click="_deviceCheckboxClicked"/></span>
            <span>[[item.suffix]]</span>
            <template is="dom-if" if="[[item.ignoredMsg]]">
              <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
              <paper-tooltip position="right" animation-delay="0">[[item.ignoredMsg]]</paper-tooltip>
            </template>
          </div>
        </template>
      </div>
    </template>
    <template is="dom-if" if="[[_equals(colorBy, 'structure')]]">
      <div class="color-text">
        <div class="color-legend-row">
          <div style="position: absolute;">
            colors
          </div>
          <span class="color-legend-value">same substructure</span>
        </div>
        <div class="color-legend-row">
          <svg>
            <use xmlns:xlink="http://www.w3.org/1999/xlink"
                 xlink:href="#grey-rect" x="0" y="0"/>
          </svg>
          <span class="color-legend-value">unique substructure</span>
        </div>
      </div>
    </template>
    <template is="dom-if" if="[[_equals(colorBy, 'device')]]">
      <div>
        <template is="dom-repeat" items="[[_currentDeviceParams]]">
          <div class="color-legend-row">
            <svg>
              <use xmlns:xlink="http://www.w3.org/1999/xlink"
                   xlink:href="#unfilled-rect" x="0" y="0" style="fill:[[item.color]]"/>
            </svg>
            <span class="color-legend-value">[[item.device]]</span>
          </div>
        </template>
        <div class="color-legend-row">
          <svg>
            <use xmlns:xlink="http://www.w3.org/1999/xlink"
                 xlink:href="#grey-rect" x="0" y="0"/>
          </svg>
          <span class="color-legend-value">unknown device</span>
        </div>
      </div>
    </template>
    <template is="dom-if" if="[[_equals(colorBy, 'xla_cluster')]]">
      <div class="color-text">
        <div class="xlaClusterList">
          <table>
          <template is="dom-repeat" items="[[_currentXlaClusterParams]]">
            <tr>
              <td style$="[[_getBackgroundColor(item.color)]]">
                <div class="colorBox"></div>
              </td>
              <td>
                <div>[[item.xla_cluster]]</div>
              </td>
            </tr>
          </template>
          </table>
        </div>
        <br/>
        <div class="color-legend-row">
          <svg>
            <use xmlns:xlink="http://www.w3.org/1999/xlink"
                 xlink:href="#grey-rect" x="0" y="0"/>
          </svg>
          <span class="color-legend-value">unknown XLA cluster</span>
        </div>
      </div>
    </template>
    <template is="dom-if" if="[[_equals(colorBy, 'op_compatibility')]]">
      <div class="color-text">
        <div class="color-legend-row">
          <svg class="icon" preserveAspectRatio="xMinYMid meet"
               viewBox="0 0 10 10">
            <use xlink:href="#op-node-stamp" fill="#0f9d58" stroke="#ccc" x="9.5"
                 y="6" />
          </svg>
          <span class="color-legend-value">Valid Op</span>
        </div>
        <div class="color-legend-row">
          <svg class="icon" preserveAspectRatio="xMinYMid meet"
               viewBox="0 0 10 10">
            <use xlink:href="#op-node-stamp" fill="#db4437" stroke="#ccc" x="9.5"
                 y="6" />
          </svg>
          <span class="color-legend-value">Invalid Op</span>
        </div>
      </div>
    </template>
    <template is="dom-if" if="[[_statsNotNull(stats)]]">
      <div class="color-legend-row">
        <svg>
          <use xmlns:xlink="http://www.w3.org/1999/xlink"
                xlink:href="#faded-rect" x="0" y="0"/>
        </svg>
        <span class="color-legend-value">unused substructure</span>
      </div>
    </template>
  </div>

  <div class="legend-holder">
    <paper-icon-button
      icon="[[_getToggleLegendIcon(_legendOpened)]]"
      on-click="_toggleLegendOpen"
      class="toggle-legend-button">
    </paper-icon-button>
    <span class="toggle-legend-text">
      [[_getToggleText(_legendOpened)]]
    </span>
    <iron-collapse opened="[[_legendOpened]]">
      <div>
        <table>
          <tr>
            <td><div class="title">Graph</div></td>
            <td>(* = expandable)</td>
          </tr>
          <tr>
            <td>
              <svg class="icon">
                <rect transform="translate(3, 1)" height="14" width="35"
                    rx="5" ry="5"/>
              </svg>
            </td>
            <td>
              Namespace<span class="gray">*</span>
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Encapsulates a set of nodes. Namespace is hierarchical and based on scope.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon" preserveAspectRatio="xMinYMid meet"
                  viewBox="0 0 10 10">
                <use xlink:href="#op-node-stamp" fill="white" stroke="#ccc" x="9.5"
                  y="6" />
              </svg>
            </td>
            <td>
              OpNode
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Node that performs an operation. These nodes cannot expand.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon" height="15px" preserveAspectRatio="xMinYMid meet"
                  viewBox="0 0 12 12">
                <use xlink:href="#op-series-horizontal-stamp" fill="white"
                    stroke="#ccc" x="2" y="2"/>
              </svg>
            </td>
            <td>
              Unconnected series<span class="gray">*</span>
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Sequence of numbered nodes that are not connected to each other.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon" height="15px"
                  preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
                <use xlink:href="#op-series-vertical-stamp"
                    fill="white" stroke="#ccc" x="2" y="2"/>
              </svg>
            </td>
            <td>
              Connected series<span class="gray">*</span>
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Sequence of numbered nodes that are connected to each other.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon">
                <circle fill="white" stroke="#848484" cx="10" cy="10" r="5"/>
              </svg>
            </td>
            <td>
              Constant
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Node that outputs a constant value.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="image-icon" viewBox="0 0 12 12" width="24" height="24">
                <use x="0" y="0" class="image-icon" xlink:href="#summary-icon"/>
              </svg>
            </td>
            <td>
              Summary
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Node that collects data for visualization within TensorBoard.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon" height="15px"
                  preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
                <defs>
                  <marker id="dataflow-arrowhead-legend" fill="#bbb" markerWidth="10"
                      markerHeight="10" refX="9" refY="5" orient="auto-start-reverse">
                    <path d="M 0,0 L 10,5 L 0,10 C 3,7 3,3 0,0"/>
                  </marker>
                </defs>
                <path marker-end="url(#dataflow-arrowhead-legend)"
                      stroke="#bbb" d="M2 9 l 29 0"
                      stroke-linecap="round" />
              </svg>
            </td>
            <td>
              Dataflow edge
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Edge showing the data flow between operations. Edges flow upwards unless arrowheads specify otherwise.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon" height="15px"
                  preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
                <path stroke="#bbb"
                  d="M2 9 l 29 0" stroke-linecap="round" stroke-dasharray="2, 2" />
              </svg>
            </td>
            <td>
              Control dependency edge
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Edge showing the control dependency between operations.
                </paper-tooltip>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <svg class="icon" height="15px"
                  preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
                <defs>
                  <marker id="reference-arrowhead-legend" fill="#FFB74D" markerWidth="10"
                      markerHeight="10" refX="9" refY="5" orient="auto-start-reverse">
                    <path d="M 0,0 L 10,5 L 0,10 C 3,7 3,3 0,0"/>
                  </marker>
                </defs>
                <path marker-end="url(#reference-arrowhead-legend)"
                      stroke="#FFB74D" d="M2 9 l 29 0"
                      stroke-linecap="round" />
              </svg>
            </td>
            <td>
              Reference edge
              <div class="legend-clarifier">
                <span>?</span>
                <paper-tooltip animation-delay="0" position="right">
                  Edge showing that the outgoing operation node can mutate the incoming tensor.
                </paper-tooltip>
              </div>
            </td>
          </tr>
        </table>
      </div>
    </iron-collapse>
  </div>
</div>
</template>
</dom-module>

<script>
(function() { // Private scope.
/**
 * Display only devices matching one of the following regex.
 */
var DEVICE_NAMES_INCLUDE = [
  {
    // Don't include GPU stream, memcpy, etc. devices
    regex: /device:[^:]+:[0-9]+$/,
  }
];

/**
 * Stats from device names that match these regexes will be disabled by default.
 * The user can still turn on a device by selecting the checkbox in the device list.
 */
var DEVICE_STATS_DEFAULT_OFF = [
//  {
//    regex: //,
//    msg: 'Excluded by default since...'
//  }
];

Polymer({
  is: 'tf-graph-controls',
  properties: {
    // Public API.
    stats: {
      value: null,
      type: Object,
      observer: '_statsChanged'
    },
    devicesForStats: {
      value: null,
      type: Object,
      notify: true,
      readonly: true,
    },
    colorBy: {
      type: String,
      value: 'structure',
      notify: true,
      readonly: true
    },
    colorByParams: {
      type: Object,
      notify: true,
      readonly: true,
    },
    datasets: {
      type: Array,
      observer: '_datasetsChanged'
    },
    renderHierarchy: {
      type: Object,
      notify: true,
    },
    metadataTags: {
      type: Array,
      computed: '_getMetadataTags(selectedDataset, datasets)'
    },
    selectedDataset: {
      type: Number,
      notify: true,
      value: 0,
      observer: '_selectedDatasetChanged'
    },
    selectedFile: {
      type: Object,
      notify: true
    },
    selectedMetadataTag: {
      type: Number,
      notify: true,
      value: -1
    },
    _currentDevices: {
      type: Array,
      computed: '_getCurrentDevices(devicesForStats)'
    },
    _currentDeviceParams: {
      type: Array,
      computed: '_getCurrentDeviceParams(colorByParams)'
    },
    _currentXlaClusterParams: {
      type: Array,
      computed: '_getCurrentXlaClusterParams(colorByParams)'
    },
    _currentGradientParams: {
      type: Object,
      computed: '_getCurrentGradientParams(colorByParams, colorBy)'
    },
    showSessionRunsDropdown: {
      type: Boolean,
      value: true
    },
    showUploadButton: {
      type: Boolean,
      value: true
    },
    // This stores whether the feature for showing health pills is enabled in the first place.
    healthPillsFeatureEnabled: Boolean,
    // This stores whether to show health pills. Only relevant if healthPillsFeatureEnabled. The
    // user can toggle this value.
    healthPillsToggledOn: {
      type: Boolean,
      notify: true,
    },
    _legendOpened: {
      type: Boolean,
      value: true,
    },
  },
  listeners: {
    'trace-inputs.change': '_traceInputToggleChanged'
  },
  _traceInputToggleChanged: function(event) {
    // Flip the state of the trace inputs flag.
    this.renderHierarchy.traceInputs = event.target.active;
    tf.graph.scene.node.traceInputs(this.renderHierarchy);
  },
  _xlaClustersProvided: function(renderHierarchy) {
    return renderHierarchy &&
        renderHierarchy.hierarchy &&
        renderHierarchy.hierarchy.xlaClusters.length > 0;
  },
  _statsChanged: function(stats) {
    if (stats == null) {
      return;
    }
    var devicesForStats = {};
    var devices = _.each(stats.dev_stats, function(d) {
      // Only considered included devices.
      var include = _.some(DEVICE_NAMES_INCLUDE, function(rule) {
        return rule.regex.test(d.device);
      });
      // Exclude device names that are ignored by default.
      var exclude = _.some(DEVICE_STATS_DEFAULT_OFF, function(rule) {
        return rule.regex.test(d.device);
      });
      if (include && !exclude) {
        devicesForStats[d.device] = true;
      }
    });
    this.set('devicesForStats', devicesForStats);
  },
  _getCurrentDevices: function(devicesForStats) {
    var all_devices = _.map(this.stats && this.stats.dev_stats, function(d) {
      return d.device;
    });
    var devices = _.filter(all_devices, function(d) {
      return _.some(DEVICE_NAMES_INCLUDE, function(rule) {
        return rule.regex.test(d);
      });
    });
    // Devices names can be long so we remove the longest common prefix
    // before showing the devices in a list.
    var suffixes = tf.graph.util.removeCommonPrefix(devices);
    if (suffixes.length == 1) {
      var found = suffixes[0].match(/device:([^:]+:[0-9]+)$/);
      if (found) {
        suffixes[0] = found[1];
      }
    }
    return _.map(devices, function(device, i) {
      var ignoredMsg = null;
      _.each(DEVICE_STATS_DEFAULT_OFF, function(rule) {
        if (rule.regex.test(device)) {
          ignoredMsg = rule.msg;
        }
      });
      return {
        device: device,
        suffix: suffixes[i],
        used: devicesForStats[device],
        ignoredMsg: ignoredMsg
      };
    });
  },
  _deviceCheckboxClicked: function(checkbox) {
    // Update the device map.
    var devicesForStats = _.extend({}, this.devicesForStats);
    var device = checkbox.target.value;
    if (checkbox.target.checked) {
      devicesForStats[device] = true;
    } else {
      delete devicesForStats[device];
    }
    this.set('devicesForStats', devicesForStats);
  },
  _numSessionRuns: function(metadataTags) {
    return metadataTags != null ? metadataTags.length : 0;
  },
  _getBackgroundColor: function(color) {
    return 'background-color:' + color;
  },
  fit: function() {
    document.querySelector('#scene').fit();
  },
  _isGradientColoring: function(stats, colorBy) {
    return ["compute_time", "memory"].indexOf(colorBy) !== -1
        && stats != null;
  },
  _equals: function(a, b) {
    return a === b;
  },
  _getCurrentDeviceParams: function(colorByParams) {
    var deviceParams = _.filter(colorByParams.device, function(param) {
      return _.some(DEVICE_NAMES_INCLUDE, function(rule) {
        return rule.regex.test(param.device);
      });
    });
    // Remove common prefix and merge back corresponding color. If
    // there is only one device then remove everything up to "/device:".
    var suffixes = tf.graph.util.removeCommonPrefix(
            _.map(deviceParams, function(d) { return d.device; }));
    if (suffixes.length == 1) {
      var found = suffixes[0].match(/device:([^:]+:[0-9]+)$/);
      if (found) {
        suffixes[0] = found[1];
      }
    }
    return _.map(deviceParams, function(d, i) {
      return { device : suffixes[i], color : d.color };
    });
  },
  _getCurrentXlaClusterParams: function(colorByParams) {
    return colorByParams.xla_cluster;
  },
  _getCurrentGradientParams: function(colorByParams, colorBy) {
    if (!this._isGradientColoring(this.stats, colorBy)) {
      return;
    }
    var params = colorByParams[colorBy];
    var minValue = params.minValue;
    var maxValue = params.maxValue;
    if (colorBy === 'memory') {
      minValue = tf.graph.util.convertUnitsToHumanReadable(
          minValue, tf.graph.util.MEMORY_UNITS);
      maxValue = tf.graph.util.convertUnitsToHumanReadable(
          maxValue, tf.graph.util.MEMORY_UNITS);
    } else if (colorBy === 'compute_time') {
      minValue = tf.graph.util.convertUnitsToHumanReadable(
          minValue, tf.graph.util.TIME_UNITS);
      maxValue = tf.graph.util.convertUnitsToHumanReadable(
          maxValue, tf.graph.util.TIME_UNITS);
    }
    return {
      minValue: minValue,
      maxValue: maxValue,
      startColor: params.startColor,
      endColor: params.endColor
    };
  },
  download: function() {
    this.$.graphdownload.click();
  },
  _updateFileInput: function(e) {
    var file = e.target.files[0];
    if (!file) {
      return;
    }
    this._setDownloadFilename(file.name);
    this.set('selectedFile', e);
  },
  _datasetsChanged: function(newDatasets, oldDatasets) {
    if (oldDatasets != null || this.selected == null) {
      // Select the first dataset by default.
      this.set('selectedDataset', 0);
      this._setDownloadFilename(this.datasets[this.selectedDataset].path);
    }
  },
  _getMetadataTags: function(selectedDataset, datasets) {
    return this.datasets[selectedDataset].runMetadata;
  },
  _selectedDatasetChanged: function(newDataset, oldDataset) {
    if (this.datasets) {
      this.set('selectedMetadataTag', -1);
      this.set('colorBy', 'structure');
      this.$['trace-inputs'].active = false; // Set trace input to off-state.
      this._setDownloadFilename(this.datasets[newDataset].path);
    }
  },
  _getFile: function() {
    this.$$("#file").click();
  },
  _setDownloadFilename: function(graphPath) {
    // Strip off everything before the last "/" and strip off the file
    // extension in order to get the name of the PNG for the graph.
    var dotIndex = graphPath.lastIndexOf('.');
    if (dotIndex) {
      graphPath = graphPath.substring(0, dotIndex);
    }
    var slashIndex = graphPath.lastIndexOf('/');
    if (slashIndex) {
      graphPath = graphPath.substring(slashIndex + 1);
    }
    this.$.graphdownload.setAttribute('download', graphPath + '.png');
  },
  _statsNotNull: function(stats) {
    return stats !== null;
  },
  _toggleLegendOpen() {
    this.set('_legendOpened', !this._legendOpened);
  },
  _getToggleText(legendOpened) {
    return legendOpened ? "Close legend." : "Expand legend.";
  },
  _getToggleLegendIcon(legendOpened) {
    // This seems counter-intuitive, but actually makes sense because the
    // expand-more button points downwards, and the expand-less button points
    // upwards. For most collapsibles, this works because the collapsibles
    // expand in the downwards direction. This collapsible expands upwards
    // though, so we reverse the icons.
    return legendOpened ? "expand-more" : "expand-less";
  },
});
})(); // Closing private scope.
</script>
